



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    
    <title>模块开发 &mdash; Nginx开发从入门到精通</title>
    <link rel="stylesheet" href="_static/book.css" type="text/css" />
    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
    <link rel="stylesheet" href="_static/comment.css" type="text/css" />
    <script type="text/javascript">
      var DOCUMENTATION_OPTIONS = {
        URL_ROOT:    '',
        VERSION:     '0.1',
        COLLAPSE_INDEX: false,
        FILE_SUFFIX: '.html',
        HAS_SOURCE:  true
      };
    </script>
    <script type="text/javascript" src="../js/??ga.js,correctpng.js?v=1"></script>
    <script type="text/javascript" src="_static/jquery.js"></script>
    <script type="text/javascript" src="_static/underscore.js"></script>
    <script type="text/javascript" src="_static/doctools.js"></script>
    <script type="text/javascript" src="_static/booktools.js"></script>
    <script type="text/javascript" src="_static/translations.js"></script>
    <script type="text/javascript" src="_static/comment.js"></script>
    <link rel="top" title="Nginx开发从入门到精通" href="index.html" /> 
  </head>
  <body lang="zh-Hans">
    <div class="related">
      <h3>导航</h3>
      <ul>
        <li class="right" style="margin-right: 10px">
          <a href="genindex.html" title="总目录"
             accesskey="I">索引</a></li>
        <li><a href="index.html">Nginx开发从入门到精通</a> &raquo;</li> 
      </ul>
    </div>  

    <div class="document">
      <div class="documentwrapper">
        <div class="bodywrapper">
          <div class="body">
            
  <div class="section" id="id1">
<h1>模块开发<a class="headerlink" href="#id1" title="永久链接至标题">¶</a></h1>
<div class="section" id="upstream">
<h2>upstream模块<a class="headerlink" href="#upstream" title="永久链接至标题">¶</a></h2>
<p>nginx模块一般被分成三大类：handler、filter和upstream。前面的章节中，读者已经了解了handler、filter。利用这两类模块，可以使nginx轻松完成任何单机工作。而本章介绍的upstream，将使nginx将跨越单机的限制，完成网络数据的接收、处理和转发。</p>
<p>数据转发功能，为nginx提供了跨越单机的横向处理能力，使nginx摆脱只能为终端节点提供单一功能的限制，而使它具备了网路应用级别的拆分、封装和整合的战略功能。在云模型大行其道的今天，数据转发使nginx有能力构建一个网络应用的关键组件。当然，一个网络应用的关键组件往往一开始都会考虑通过高级开发语言编写，因为开发比较方便，但系统到达一定规模，需要更重视性能的时候，这些高级语言为了达成目标所做的结构化修改所付出的代价会使nginx的upstream模块就呈现出极大的吸引力，因为他天生就快。作为附带，nginx的配置提供的层次化和松耦合使得系统的扩展性也可能达到比较高的程度。</p>
<p>言归正传，下面介绍upstream的写法。</p>
<div class="section" id="id2">
<h3>upstream模块接口<a class="headerlink" href="#id2" title="永久链接至标题">¶</a></h3>
<p>从本质上说，upstream属于handler，只是他不产生自己的内容，而是通过请求后端服务器得到内容，所以才称为upstream（上游）。请求并取得响应内容的整个过程已经被封装到nginx内部，所以upstream模块只需要开发若干回调函数，完成构造请求和解析响应等具体的工作。</p>
<p>这些回调函数如下表所示：</p>
<table border="1" class="docutils">
<colgroup>
<col width="23%" />
<col width="77%" />
</colgroup>
<tbody valign="top">
<tr class="row-odd"><td>create_request</td>
<td>生成发送到后端服务器的请求缓冲（缓冲链）。</td>
</tr>
<tr class="row-even"><td>reinit_request</td>
<td>在某台后端服务器出错的情况，nginx会尝试另一台后端服务器。
nginx选定新的服务器以后，会先调用此函数，然后再次调用
create_request，以重新初始化upstream模块的工作状态。</td>
</tr>
<tr class="row-odd"><td>process_header</td>
<td>处理后端服务器返回的信息头部。所谓头部是与upstream server
通信的协议规定的，比如HTTP协议的header部分，或者memcached
协议的响应状态部分。</td>
</tr>
<tr class="row-even"><td>abort_request</td>
<td>在客户端放弃请求时被调用。不需要在函数中实现关闭后端服务
器连接的功能，系统会自动完成关闭连接的步骤，所以一般此函
数不会进行任何具体工作。</td>
</tr>
<tr class="row-odd"><td>finalize_request</td>
<td>正常完成与后端服务器的请求后调用该函数，与abort_request
相同，一般也不会进行任何具体工作。</td>
</tr>
<tr class="row-even"><td>input_filter</td>
<td>处理后端服务器返回的响应正文。nginx默认的input_filter会
将收到的内容封装成为缓冲区链ngx_chain。该链由upstream的
out_bufs指针域定位，所以开发人员可以在模块以外通过该指针
得到后端服务器返回的正文数据。memcached模块实现了自己的
input_filter，在后面会具体分析这个模块。</td>
</tr>
<tr class="row-odd"><td>input_filter_init</td>
<td>初始化input filter的上下文。nginx默认的input_filter_init
直接返回。</td>
</tr>
</tbody>
</table>
</div>
<div class="section" id="memcached">
<h3>memcached模块分析<a class="headerlink" href="#memcached" title="永久链接至标题">¶</a></h3>
<p>memcache是一款高性能的分布式cache系统，得到了非常广泛的应用。memcache定义了一套私有通信协议，使得不能通过HTTP请求来访问memcache。但协议本身简单高效，而且memcache使用广泛，所以大部分现代开发语言和平台都提供了memcache支持，方便开发者使用memcache。</p>
<p>nginx提供了ngx_http_memcached模块，提供从memcache读取数据的功能，而不提供向memcache写数据的功能。作为web服务器，这种设计是可以接受的。</p>
<p>下面，我们开始分析ngx_http_memcached模块，一窥upstream的奥秘。</p>
<div class="section" id="handler">
<h4>Handler模块？<a class="headerlink" href="#handler" title="永久链接至标题">¶</a></h4>
<p>初看memcached模块，大家可能觉得并无特别之处。如果稍微细看，甚至觉得有点像handler模块，当大家看到这段代码以后，必定疑惑为什么会跟handler模块一模一样。</p>
<div class="highlight-none"><div class="highlight"><pre>clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf-&gt;handler = ngx_http_memcached_handler;
</pre></div>
</div>
<p>因为upstream模块使用的就是handler模块的接入方式。同时，upstream模块的指令系统的设计也是遵循handler模块的基本规则：配置该模块才会执行该模块。</p>
<div class="highlight-none"><div class="highlight"><pre>{ ngx_string(&quot;memcached_pass&quot;),
  NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
  ngx_http_memcached_pass,
  NGX_HTTP_LOC_CONF_OFFSET,
  0,
  NULL }
</pre></div>
</div>
<p>所以大家觉得眼熟是好事，说明大家对Handler的写法已经很熟悉了。</p>
</div>
<div class="section" id="id3">
<h4>Upstream模块！<a class="headerlink" href="#id3" title="永久链接至标题">¶</a></h4>
<p>那么，upstream模块的特别之处究竟在哪里呢？答案是就在模块处理函数的实现中。upstream模块的处理函数进行的操作都包含一个固定的流程。在memcached的例子中，可以观察ngx_http_memcached_handler的代码，可以发现，这个固定的操作流程是：</p>
<p>1. 创建upstream数据结构。</p>
<div class="highlight-none"><div class="highlight"><pre>if (ngx_http_upstream_create(r) != NGX_OK) {
    return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
</pre></div>
</div>
<p>2. 设置模块的tag和schema。schema现在只会用于日志，tag会用于buf_chain管理。</p>
<div class="highlight-none"><div class="highlight"><pre>u = r-&gt;upstream;

ngx_str_set(&amp;u-&gt;schema, &quot;memcached://&quot;);
u-&gt;output.tag = (ngx_buf_tag_t) &amp;ngx_http_memcached_module;
</pre></div>
</div>
<p>3. 设置upstream的后端服务器列表数据结构。</p>
<div class="highlight-none"><div class="highlight"><pre>mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
u-&gt;conf = &amp;mlcf-&gt;upstream;
</pre></div>
</div>
<p>4. 设置upstream回调函数。在这里列出的代码稍稍调整了代码顺序。</p>
<div class="highlight-none"><div class="highlight"><pre>u-&gt;create_request = ngx_http_memcached_create_request;
u-&gt;reinit_request = ngx_http_memcached_reinit_request;
u-&gt;process_header = ngx_http_memcached_process_header;
u-&gt;abort_request = ngx_http_memcached_abort_request;
u-&gt;finalize_request = ngx_http_memcached_finalize_request;
u-&gt;input_filter_init = ngx_http_memcached_filter_init;
u-&gt;input_filter = ngx_http_memcached_filter;
</pre></div>
</div>
<p>5. 创建并设置upstream环境数据结构。</p>
<div class="highlight-none"><div class="highlight"><pre>ctx = ngx_palloc(r-&gt;pool, sizeof(ngx_http_memcached_ctx_t));
if (ctx == NULL) {
    return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

ctx-&gt;rest = NGX_HTTP_MEMCACHED_END;
ctx-&gt;request = r;

ngx_http_set_ctx(r, ctx, ngx_http_memcached_module);

u-&gt;input_filter_ctx = ctx;
</pre></div>
</div>
<p>6. 完成upstream初始化并进行收尾工作。</p>
<div class="highlight-none"><div class="highlight"><pre>r-&gt;main-&gt;count++;
ngx_http_upstream_init(r);
return NGX_DONE;
</pre></div>
</div>
<p>任何upstream模块，简单如memcached，复杂如proxy、fastcgi都是如此。不同的upstream模块在这6步中的最大差别会出现在第2、3、4、5上。其中第2、4两步很容易理解，不同的模块设置的标志和使用的回调函数肯定不同。第5步也不难理解，只有第3步是最为晦涩的，不同的模块在取得后端服务器列表时，策略的差异非常大，有如memcached这样简单明了的，也有如proxy那样逻辑复杂的。这个问题先记下来，等把memcached剖析清楚了，再单独讨论。</p>
<p>第6步是一个常态。将count加1，然后返回NGX_DONE。nginx遇到这种情况，虽然会认为当前请求的处理已经结束，但是不会释放请求使用的内存资源，也不会关闭与客户端的连接。之所以需要这样，是因为nginx建立了upstream请求和客户端请求之间一对一的关系，在后续使用ngx_event_pipe将upstream响应发送回客户端时，还要使用到这些保存着客户端信息的数据结构。这部分会在后面的原理篇做具体介绍，这里不再展开。</p>
<p>将upstream请求和客户端请求进行一对一绑定，这个设计有优势也有缺陷。优势就是简化模块开发，可以将精力集中在模块逻辑上，而缺陷同样明显，一对一的设计很多时候都不能满足复杂逻辑的需要。对于这一点，将会在后面的原理篇来阐述。</p>
</div>
<div class="section" id="id4">
<h4>回调函数<a class="headerlink" href="#id4" title="永久链接至标题">¶</a></h4>
<p>前面剖析了memcached模块的骨架，现在开始逐个解决每个回调函数。</p>
<p>1. ngx_http_memcached_create_request：很简单的按照设置的内容生成一个key，接着生成一个“get $key”的请求，放在r-&gt;upstream-&gt;request_bufs里面。</p>
<p>2. ngx_http_memcached_reinit_request：无需初始化。</p>
<p>3. ngx_http_memcached_abort_request：无需额外操作。</p>
<p>4. ngx_http_memcached_finalize_request：无需额外操作。</p>
<p>5. ngx_http_memcached_process_header：模块的业务重点函数。memcache协议将头部信息被定义为第一行文本，可以找到这段代码证明：</p>
<div class="highlight-none"><div class="highlight"><pre>for (p = u-&gt;buffer.pos; p &lt; u-&gt;buffer.last; p++) {
    if ( * p == LF) {
    goto found;
}
</pre></div>
</div>
<p>如果在已读入缓冲的数据中没有发现LF(&#8216;n&#8217;)字符，函数返回NGX_AGAIN，表示头部未完全读入，需要继续读取数据。nginx在收到新的数据以后会再次调用该函数。</p>
<p>nginx处理后端服务器的响应头时只会使用一块缓存，所有数据都在这块缓存中，所以解析头部信息时不需要考虑头部信息跨越多块缓存的情况。而如果头部过大，不能保存在这块缓存中，nginx会返回错误信息给客户端，并记录error log，提示缓存不够大。</p>
<p>process_header的重要职责是将后端服务器返回的状态翻译成返回给客户端的状态。例如，在ngx_http_memcached_process_header中，有这样几段代码：</p>
<div class="highlight-none"><div class="highlight"><pre>r-&gt;headers_out.content_length_n = ngx_atoof(len, p - len - 1);

u-&gt;headers_in.status_n = 200;
u-&gt;state-&gt;status = 200;

u-&gt;headers_in.status_n = 404;
u-&gt;state-&gt;status = 404;
</pre></div>
</div>
<p>u-&gt;state用于计算upstream相关的变量。比如u-&gt;status-&gt;status将被用于计算变量“upstream_status”的值。u-&gt;headers_in将被作为返回给客户端的响应返回状态码。而第一行则是设置返回给客户端的响应的长度。</p>
<p>在这个函数中不能忘记的一件事情是处理完头部信息以后需要将读指针pos后移，否则这段数据也将被复制到返回给客户端的响应的正文中，进而导致正文内容不正确。</p>
<div class="highlight-none"><div class="highlight"><pre>u-&gt;buffer.pos = p + 1;
</pre></div>
</div>
<p>process_header函数完成响应头的正确处理，应该返回NGX_OK。如果返回NGX_AGAIN，表示未读取完整数据，需要从后端服务器继续读取数据。返回NGX_DECLINED无意义，其他任何返回值都被认为是出错状态，nginx将结束upstream请求并返回错误信息。</p>
<p>6. ngx_http_memcached_filter_init：修正从后端服务器收到的内容长度。因为在处理header时没有加上这部分长度。</p>
<p>7. ngx_http_memcached_filter：memcached模块是少有的带有处理正文的回调函数的模块。因为memcached模块需要过滤正文末尾CRLF &#8220;END&#8221; CRLF，所以实现了自己的filter回调函数。处理正文的实际意义是将从后端服务器收到的正文有效内容封装成ngx_chain_t，并加在u-&gt;out_bufs末尾。nginx并不进行数据拷贝，而是建立ngx_buf_t数据结构指向这些数据内存区，然后由ngx_chain_t组织这些buf。这种实现避免了内存大量搬迁，也是nginx高效的奥秘之一。</p>
</div>
</div>
<div class="section" id="id5">
<h3>本节小结<a class="headerlink" href="#id5" title="永久链接至标题">¶</a></h3>
<p>在这一节里，大家对upstream模块的基本组成有了一些认识。upstream模块是从handler模块发展而来，指令系统和模块生效方式与handler模块无异。不同之处在于，upstream模块在handler函数中设置众多回调函数。实际工作都是由这些回调函数完成的。每个回调函数都是在upstream的某个固定阶段执行，各司其职，大部分回调函数一般不会真正用到。upstream最重要的回调函数是create_request、process_header和input_filter，他们共同实现了与后端服务器的协议的解析部分。</p>
</div>
</div>
</div>


          </div>
        </div>
      </div>
      <div class="sphinxsidebar">
        <div class="sphinxsidebarwrapper">
            <p class="logo"><a href="http://tengine.taobao.org/">
              <img class="logo" src="_static/logo.png" alt="Logo"/>
            </a></p>
  <h3><a href="index.html">內容目录</a></h3>
  <ul>
<li><a class="reference internal" href="#">模块开发</a><ul>
<li><a class="reference internal" href="#upstream">upstream模块</a><ul>
<li><a class="reference internal" href="#id2">upstream模块接口</a></li>
<li><a class="reference internal" href="#memcached">memcached模块分析</a><ul>
<li><a class="reference internal" href="#handler">Handler模块？</a></li>
<li><a class="reference internal" href="#id3">Upstream模块！</a></li>
<li><a class="reference internal" href="#id4">回调函数</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id5">本节小结</a></li>
</ul>
</li>
</ul>
</li>
</ul>

  <h3>本页</h3>
  <ul class="this-page-menu">
    <li><a href="_sources/example_1.txt"
           rel="nofollow">显示源代码</a></li>
  </ul>
<div id="searchbox" style="display: none">
  <h3>快速搜索</h3>
    <form class="search" action="search.html" method="get">
      <input type="text" name="q" />
      <input type="submit" value="搜索" />
      <input type="hidden" name="check_keywords" value="yes" />
      <input type="hidden" name="area" value="default" />
    </form>
    <p class="searchtip" style="font-size: 90%">
    输入相关的模块，术语，类或者函数名称进行搜索
    </p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
        </div>
      </div>
      <div class="clearer"></div>
    </div>
    <div class="related">
      <h3>导航</h3>
      <ul>
        <li class="right" style="margin-right: 10px">
          <a href="genindex.html" title="总目录"
             >索引</a></li>
        <li><a href="index.html">Nginx开发从入门到精通</a> &raquo;</li> 
      </ul>
    </div>
    <div class="footer">
        &copy; 版权所有 2012, taobao.
      使用 <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
    </div>
  </body>
</html>